/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiummath;

import java.math.BigInteger;
import java.util.Random;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.cadmium.Cadmium;

/**
 * This class provides the primitives for big integer manipulation.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.1
 */
@PrimitiveProvider
public final class BigIntegers {

    /**
     * No instance of this class.
     */
    private BigIntegers() {
    } // end empty constructor

    /**
     * Returns a big integer constant.
     * @param ctxt context
     * @param constant constant value to be promoted from int to big integer, <br/>
     *                 should be one of: zero, one, ten
     * @return the passed value, as a big integer
     * @throws Fail.Exception the passed value is not one of the following: zero, one, ten
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_get_constant(final CodeRunner ctxt,
                                                            final Value constant)
        throws Fail.Exception {
        try {
            switch (constant.asLong()) {
            case 0: return Cadmium.createObject(BigInteger.ZERO);
            case 1: return Cadmium.createObject(BigInteger.ONE);
            case 10: return Cadmium.createObject(BigInteger.TEN);
            default:
                Fail.invalidArgument("CadmiumMath.BigInteger.get_constant");
                return Value.ZERO; // never reached
            } // end switch
        } catch (final Fail.Exception fe) {
            throw fe;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_get_constant(CodeRunner, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param bytes two's complement (big-endian) binary representation of the big integer
     * @return a new big integer
     * @throws Fail.Exception if <i>bytes</i> is empty
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_bytes(final CodeRunner ctxt,
                                                             final Value bytes)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(bytes.asBlock().getBytes()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_bytes(CodeRunner, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param signum sign of the big integer
     * @param bytes big-endian representation of the number
     * @return a new big integer
     * @throws Fail.Exception if any parameters is invalid
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_signum_bytes(final CodeRunner ctxt,
                                                                    final Value signum,
                                                                    final Value bytes)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(signum.asLong(),
                                                       bytes.asBlock().getBytes()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_signum_bytes(CodeRunner, Value, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param length bit length of the big integer
     * @param certainty uncertainty of the process: the probability that the
     *                  returned number will be a prime is <i>1 - 0.5**certainty</i>
     * @param random random number generator
     * @return a new big integer
     * @throws Fail.Exception if <i>length</i> is below 2
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_prime_of_random(final CodeRunner ctxt,
                                                                    final Value length,
                                                                    final Value certainty,
                                                                    final Value random)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(length.asLong(),
                                                       certainty.asLong(),
                                                       (Random) random.asBlock().asCustom()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_prime_of_random(CodeRunner, Value, Value, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param length bit length of the big integer
     * @param random random number generator
     * @return a new big integer
     * @throws Fail.Exception if <i>length</i> is negative
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_random(final CodeRunner ctxt,
                                                              final Value length,
                                                              final Value random)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(length.asLong(),
                                                       (Random) random.asBlock().asCustom()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_random(CodeRunner, Value, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param string string to convert into a big integer
     * @return a new big integer
     * @throws Fail.Exception if <i>string</i> is invalid
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_string(final CodeRunner ctxt,
                                                              final Value string)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(string.asBlock().asString()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_string(CodeRunner, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param string string to convert into a big integer
     * @param radix radix of the string representation
     * @return a new big integer
     * @throws Fail.Exception if <i>string</i> is invalid
     * @throws Fail.Exception if <i>radix</i> is invalid
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_string_radix(final CodeRunner ctxt,
                                                                    final Value string,
                                                                    final Value radix)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(new BigInteger(string.asBlock().asString(),
                                                       radix.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_string_radix(CodeRunner, Value, Value)'

    /**
     * Creates a new big integer.
     * @param ctxt context
     * @param x int64 value
     * @return a new big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_make_of_int64(final CodeRunner ctxt,
                                                             final Value x)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(BigInteger.valueOf(x.asBlock().asInt64()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_make_of_int64(CodeRunner, Value)'

    /**
     * Returns the absolute value of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the absolute value of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_abs(final CodeRunner ctxt,
                                                   final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.abs());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_abs(CodeRunner, Value)'

    /**
     * Returns the addition of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the addition of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_add(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.add(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_add(CodeRunner, Value, Value)'

    /**
     * Returns the and of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the and of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_and_(final CodeRunner ctxt,
                                                    final Value biginteger,
                                                    final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.and(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_and_(CodeRunner, Value, Value)'

    /**
     * Returns the logical and of the first passed value with the negation of the second one.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the logical and of the first passed value with the negation of the second one
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_and_not(final CodeRunner ctxt,
                                                       final Value biginteger,
                                                       final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.andNot(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_and_not(CodeRunner, Value, Value)'

    /**
     * Returns the bit count of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the bit count of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_bit_count(final CodeRunner ctxt,
                                                         final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Value.createFromLong(bi.bitCount());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_bit_count(CodeRunner, Value)'

    /**
     * Returns the bit count of the passed big integer (excluding sign bit).
     * @param ctxt context
     * @param biginteger big integer
     * @return the bit count of the passed big integer (excluding sign bit)
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_bit_length(final CodeRunner ctxt,
                                                          final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Value.createFromLong(bi.bitLength());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_bit_length(CodeRunner, Value)'

    /**
     * Returns a big integer equal to the passed one, except that a bit has been
     * cleared.
     * @param ctxt context
     * @param biginteger big integer
     * @param n index of bit to clear
     * @return <i>biginteger and not (1 &lt;&lt; n)</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_clear_bit(final CodeRunner ctxt,
                                                         final Value biginteger,
                                                         final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.clearBit(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_clear_bit(CodeRunner, Value, Value)'

    /**
     * Compares the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return -1, 0, or 1 as <i>biginteger</i> is less than, equal to, or greater than <i>biginteger2</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_compare_to(final CodeRunner ctxt,
                                                          final Value biginteger,
                                                          final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Value.createFromLong(bi.compareTo(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_compare_to(CodeRunner, Value, Value)'

    /**
     * Returns the quotient of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the quotient of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_divide(final CodeRunner ctxt,
                                                      final Value biginteger,
                                                      final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.divide(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_divide(CodeRunner, Value, Value)'

    /**
     * Returns the quotient and remainder of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the quotient and remainder of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_divide_and_remainder(final CodeRunner ctxt,
                                                                    final Value biginteger,
                                                                    final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            final BigInteger[] dr = bi.divideAndRemainder(bi2);
            final Block res = Block.createBlock(0,
                                                Cadmium.createObject(dr[0]),
                                                Cadmium.createObject(dr[1]));
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_divide_and_remainder(CodeRunner, Value, Value)'

    /**
     * Tests whether the passed values are equal.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return <tt>true</tt> if the passed values are equal,
     *         <tt>false</tt> otherwise
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_equals(final CodeRunner ctxt,
                                                      final Value biginteger,
                                                      final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return bi.equals(bi2) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_equals(CodeRunner, Value, Value)'

    /**
     * Returns a big integer equal to the passed one, except that a bit has been
     * flipped.
     * @param ctxt context
     * @param biginteger big integer
     * @param n index of bit to flip
     * @return <i>biginteger xor (1 &lt;&lt; n)</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_flip_bit(final CodeRunner ctxt,
                                                        final Value biginteger,
                                                        final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.flipBit(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_flip_bit(CodeRunner, Value, Value)'

    /**
     * Returns the float value of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the float value of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_float_value(final CodeRunner ctxt,
                                                           final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createDouble(bi.doubleValue());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_float_value(CodeRunner, Value)'

    /**
     * Returns the greatest common divisor of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the greatest common divisor of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_gcd(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.gcd(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_gcd(CodeRunner, Value, Value)'

    /**
     * Returns the lowest set bit of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the lowest set bit of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_get_lowest_set_bit(final CodeRunner ctxt,
                                                                  final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Value.createFromLong(bi.getLowestSetBit());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_get_lowest_set_bit(CodeRunner, Value)'

    /**
     * Returns the hash code of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the hash code of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_hash_code(final CodeRunner ctxt,
                                                         final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_32_SIZE,
                                                 Custom.INT_32_OPS);
            res.setInt32(bi.hashCode());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_hash_code(CodeRunner, Value)'

    /**
     * Returns the int value of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the int value of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_int_value(final CodeRunner ctxt,
                                                         final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Value.createFromLong(bi.intValue());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_int_value(CodeRunner, Value)'

    /**
     * Returns the int32 value of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the int32 value of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_int32_value(final CodeRunner ctxt,
                                                           final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_32_SIZE,
                                                 Custom.INT_32_OPS);
            res.setInt32(bi.intValue());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_int32_value(CodeRunner, Value)'

    /**
     * Returns the int64 value of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the int64 value of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_int64_value(final CodeRunner ctxt,
                                                           final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createCustom(Custom.INT_64_SIZE,
                                                 Custom.INT_64_OPS);
            res.setInt64(bi.longValue());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_int64_value(CodeRunner, Value)'

    /**
     * Tests whether the passed big integer is probably prime.
     * @param ctxt context
     * @param biginteger big integer
     * @param certainty uncertainty of the process: if the function returns
     *                  <tt>true</tt> the probability that the passed big
     *                  integer is prime exceeds <i>1 - 0.5**certainty</i>
     * @return <tt>true</tt> if the passed big integer is probably prime,
     *         <tt>false</tt> if it is definitely composite
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_is_probably_prime(final CodeRunner ctxt,
                                                                 final Value biginteger,
                                                                 final Value certainty)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return bi.isProbablePrime(certainty.asLong()) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_is_probably_prime(CodeRunner, Value, Value)'

    /**
     * Returns the maximum of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the maximum of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_max(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.max(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_max(CodeRunner, Value, Value)'

    /**
     * Returns the minimum of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the minimum of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_min(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.min(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_min(CodeRunner, Value, Value)'

    /**
     * Returns the modulo of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the modulo of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_mod_(final CodeRunner ctxt,
                                                    final Value biginteger,
                                                    final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.mod(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_mod_(CodeRunner, Value, Value)'

    /**
     * Returns the modulo/inverse of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return <i>biginteger**-1 mod biginteger2</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_mod_inverse(final CodeRunner ctxt,
                                                           final Value biginteger,
                                                           final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.modInverse(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_mod_inverse(CodeRunner, Value, Value)'

    /**
     * Returns a big integer of the form <i>x ** y mod z</i>.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @param biginteger3 big integer
     * @return <i>biginteger ** biginteger2 mod biginteger3</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_mod_pow(final CodeRunner ctxt,
                                                       final Value biginteger,
                                                       final Value biginteger2,
                                                       final Value biginteger3)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            final BigInteger bi3 = (BigInteger) biginteger3.asBlock().asCustom();
            return Cadmium.createObject(bi.modPow(bi2, bi3));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_mod_pow(CodeRunner, Value, Value, Value)'

    /**
     * Returns the multiplication of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the multiplication of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_multiply(final CodeRunner ctxt,
                                                        final Value biginteger,
                                                        final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.multiply(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_multiply(CodeRunner, Value, Value)'

    /**
     * Returns the opposite of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the opposite of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_negate(final CodeRunner ctxt,
                                                      final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.negate());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_negate(CodeRunner, Value)'

    /**
     * Returns the next probable prime of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the next probable prime of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_next_probable_prime(final CodeRunner ctxt,
                                                                   final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.nextProbablePrime());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_next_probable_prime(CodeRunner, Value)'

    /**
     * Returns the negation of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the negation of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_not(final CodeRunner ctxt,
                                                   final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.not());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_not(CodeRunner, Value)'

    /**
     * Returns the inclusive or of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the inclusive or of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_or_(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.or(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_or_(CodeRunner, Value, Value)'

    /**
     * Returns a big integer equal to the passed one raised to a given exponent.
     * @param ctxt context
     * @param biginteger big integer
     * @param exp exponent
     * @return <i>biginteger ** exp</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_pow(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value exp)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.pow(exp.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_pow(CodeRunner, Value, Value)'

    /**
     * Returns the remainder of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the remainder of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_remainder(final CodeRunner ctxt,
                                                         final Value biginteger,
                                                         final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.remainder(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_remainder(CodeRunner, Value, Value)'

    /**
     * Returns a big integer equal to the passed one, except that a bit has been
     * set.
     * @param ctxt context
     * @param biginteger big integer
     * @param n index of bit to set
     * @return <i>biginteger or (1 &lt;&lt; n)</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_set_bit(final CodeRunner ctxt,
                                                       final Value biginteger,
                                                       final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.setBit(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_set_bit(CodeRunner, Value, Value)'

    /**
     * Returns a big integer equal to the left-shifted passed one.
     * @param ctxt context
     * @param biginteger big integer
     * @param n shift distance (may be negative)
     * @return <i>biginteger &lt;&lt; n</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_shift_left(final CodeRunner ctxt,
                                                          final Value biginteger,
                                                          final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.shiftLeft(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_shift_left(CodeRunner, Value, Value)'

    /**
     * Returns a big integer equal to the right-shifted passed one. <br/>
     * Performs sign extension.
     * @param ctxt context
     * @param biginteger big integer
     * @param n shift distance (may be negative)
     * @return <i>biginteger &gt;&gt; n</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_shift_right(final CodeRunner ctxt,
                                                           final Value biginteger,
                                                           final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Cadmium.createObject(bi.shiftRight(n.asLong()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_shift_right(CodeRunner, Value, Value)'

    /**
     * Returns the sign of the passed big integer.
     * @param ctxt context
     * @param biginteger big integer
     * @return the sign of the passed big integer
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_signum(final CodeRunner ctxt,
                                                      final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return Value.createFromLong(bi.signum());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_signum(CodeRunner, Value)'

    /**
     * Returns the subtraction of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the subtraction of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_subtract(final CodeRunner ctxt,
                                                        final Value biginteger,
                                                        final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.subtract(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_subtract(CodeRunner, Value, Value)'

    /**
     * Tests if the given bit of the passed big integer is set.
     * @param ctxt context
     * @param biginteger big integer
     * @param n index of bit to test
     * @return <i>(biginteger or (1 &lt;&lt; n)) != 0</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_test_bit(final CodeRunner ctxt,
                                                        final Value biginteger,
                                                        final Value n)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            return bi.testBit(n.asLong()) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_test_bit(CodeRunner, Value, Value)'

    /**
     * Converts the passed big integer into a byte array.
     * @param ctxt context
     * @param biginteger big integer
     * @return the passed big integer as a byte array
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_to_bytes(final CodeRunner ctxt,
                                                        final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createString(bi.toByteArray());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_to_bytes(CodeRunner, Value)'

    /**
     * Converts the passed big integer into a string.
     * @param ctxt context
     * @param biginteger big integer
     * @return the passed big integer as a string
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_to_string(final CodeRunner ctxt,
                                                         final Value biginteger)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createString(bi.toString());
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_to_string(CodeRunner, Value)'

    /**
     * Converts the passed big integer into a string.
     * @param ctxt context
     * @param biginteger big integer
     * @param radix radix of produced string
     * @return the passed big integer as a string
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_to_string_radix(final CodeRunner ctxt,
                                                               final Value biginteger,
                                                               final Value radix)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final Block res = Block.createString(bi.toString(radix.asLong()));
            return Value.createFromBlock(res);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_to_string_radix(CodeRunner, Value, Value)'

    /**
     * Returns the exlusive or of the passed values.
     * @param ctxt context
     * @param biginteger big integer
     * @param biginteger2 big integer
     * @return the exlusive or of the passed values
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiummath_biginteger_xor(final CodeRunner ctxt,
                                                   final Value biginteger,
                                                   final Value biginteger2)
        throws Fail.Exception {
        try {
            final BigInteger bi = (BigInteger) biginteger.asBlock().asCustom();
            final BigInteger bi2 = (BigInteger) biginteger2.asBlock().asCustom();
            return Cadmium.createObject(bi.xor(bi2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiummath_biginteger_xor(CodeRunner, Value, Value)'

} // end class 'BigIntegers'
